import type { FunctionArgs } from '@vueuse/core';
import { upperFirst } from 'lodash-es';

export interface ViewportOffsetResult {
  left: number;
  top: number;
  right: number;
  bottom: number;
  rightIncludeBody: number;
  bottomIncludeBody: number;
}

/**
 * 获取元素的边界框信息。
 *
 * @param {Element} element - 要获取边界框信息的元素。
 * @returns {DOMRect|number} - 元素的边界框对象（DOMRect）或数值 0（如果元素不存在或不支持 getBoundingClientRect 方法）。
 */
export function getBoundingClientRect(element: Element): DOMRect | number {
  if (!element || !element.getBoundingClientRect) {
    return 0;
  }
  return element.getBoundingClientRect();
}

/**
 * 从字符串中去除前后的空白字符。
 *
 * @param {string} string - 要被修剪的输入字符串。
 * @returns {string} - 修剪后的字符串。
 */
function trim(string: string) {
  return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
}

/**
 * 判断元素是否拥有某个 CSS 类。
 *
 * @param {Element} el - 要判断的元素。
 * @param {string} cls - 要检查的 CSS 类名。
 * @returns {boolean} - 如果元素拥有该 CSS 类，则返回 true；否则返回 false。
 * @throws {Error} 如果 CSS 类名中包含空格，则抛出错误。
 */
export function hasClass(el: Element, cls: string) {
  if (!el || !cls) return false;
  if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
  if (el.classList) {
    return el.classList.contains(cls);
  } else {
    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
  }
}

/**
 * 给元素添加一个或多个 CSS 类。
 *
 * @param {Element} el - 要添加类的元素。
 * @param {string} cls - 要添加的 CSS 类名，可以是一个或多个类名，用空格分隔。
 * @throws {Error} 如果 CSS 类名中包含空格，则抛出错误。
 */
export function addClass(el: Element, cls: string) {
  if (!el) return;
  let curClass = el.className;
  const classes = (cls || '').split(' ');

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i];
    if (!clsName) continue;
    //元素 el 支持 classList 属性
    if (el.classList) {
      el.classList.add(clsName);
    }
    //如果元素 el 不支持 classList 属性
    else if (!hasClass(el, clsName)) {
      curClass += ' ' + clsName;
    }
  }
  if (!el.classList) {
    el.className = curClass;
  }
}

/**
 * 从元素中移除一个或多个 CSS 类。
 *
 * @param {Element} el - 要移除类的元素。
 * @param {string} cls - 要移除的 CSS 类名，可以是一个或多个类名，用空格分隔。
 * @throws {Error} 如果 CSS 类名中包含空格，则抛出错误。
 */
export function removeClass(el: Element, cls: string) {
  if (!el || !cls) return;
  const classes = cls.split(' ');
  let curClass = ' ' + el.className + ' ';

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i];
    if (!clsName) continue;

    if (el.classList) {
      el.classList.remove(clsName);
    } else if (hasClass(el, clsName)) {
      curClass = curClass.replace(' ' + clsName + ' ', ' ');
    }
  }
  if (!el.classList) {
    el.className = trim(curClass);
  }
}
/**
 * 计算元素相对于视口的偏移量
 * @param {Element} element - 待计算偏移量的元素
 * @returns {ViewportOffsetResult} - 元素相对于视口的偏移量结果对象
 * @typedef {Object} ViewportOffsetResult - 元素相对于视口的偏移量结果对象
 * @property {number} left - 元素相对于视口左侧的偏移量
 * @property {number} top - 元素相对于视口顶部的偏移量
 * @property {number} right - 元素相对于视口右侧的偏移量
 * @property {number} bottom - 元素相对于视口底部的偏移量
 * @property {number} rightIncludeBody - 元素相对于视口右侧（包括 body）的偏移量
 * @property {number} bottomIncludeBody - 元素相对于视口底部（包括 body）的偏移量
 */
export function getViewportOffset(element: Element): ViewportOffsetResult {
  // 获取当前文档的根元素
  const doc = document.documentElement;

  // 获取文档的滚动偏移
  const docScrollLeft = doc.scrollLeft;
  const docScrollTop = doc.scrollTop;

  // 获取文档的客户端边框
  const docClientLeft = doc.clientLeft;
  const docClientTop = doc.clientTop;

  // 获取窗口的页面偏移
  const pageXOffset = window.scrollX;
  const pageYOffset = window.scrollY;

  // 获取元素的边界框
  const box = getBoundingClientRect(element);

  // 从边界框中获取左侧、顶部、宽度和高度
  const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;

  // 计算滚动偏移量
  const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);
  const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);

  // 计算元素的偏移左侧和偏移顶部
  const offsetLeft = retLeft + pageXOffset;
  const offsetTop = rectTop + pageYOffset;

  // 计算相对于视口的左侧和顶部位置
  const left = offsetLeft - scrollLeft;
  const top = offsetTop - scrollTop;

  // 计算视口的宽度和高度
  const clientWidth = window.document.documentElement.clientWidth;
  const clientHeight = window.document.documentElement.clientHeight;

  // 返回结果对象，包括左侧、顶部、右侧、底部在内的各种偏移量
  return {
    left: left,
    top: top,
    right: clientWidth - rectWidth - left,
    bottom: clientHeight - rectHeight - top,
    rightIncludeBody: clientWidth - left,
    bottomIncludeBody: clientHeight - top,
  };
}

/**
 * 为指定属性添加浏览器前缀的样式值，并返回包含带前缀和不带前缀样式的对象
 * @param {string} attr - 需要添加浏览器前缀的属性
 * @param {string} value - 属性的样式值
 * @returns {Object} - 包含带前缀和不带前缀样式的对象
 */
export function hackCss(attr: string, value: string) {
  const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];

  const styleObj: any = {};
  prefix.forEach((item) => {
    styleObj[`${item}${upperFirst(attr)}`] = value;
  });
  return {
    ...styleObj,
    [attr]: value,
  };
}

/**
 * 函数名：on
 * 功能：为给定元素或文档对象添加事件监听器
 *
 * @param {Element | HTMLElement | Document | Window} element - 元素或文档对象
 * @param {string} event - 事件名称
 * @param {EventListenerOrEventListenerObject} handler - 事件处理函数
 * @returns {void}
 */
export function on(
  element: Element | HTMLElement | Document | Window,
  event: string,
  handler: EventListenerOrEventListenerObject,
): void {
  if (element && event && handler) {
    element.addEventListener(event, handler, false);
  }
}

/**
 * 函数名：off
 * 功能：移除给定元素或文档对象上的指定事件监听器
 *
 * @param {Element | HTMLElement | Document | Window} element - 元素或文档对象
 * @param {string} event - 事件名称
 * @param {Fn} handler - 事件处理函数
 * @returns {void}
 */
export function off(
  element: Element | HTMLElement | Document | Window,
  event: string,
  handler: Fn,
): void {
  if (element && event && handler) {
    element.removeEventListener(event, handler, false);
  }
}

/**
 * 函数名：once
 * 功能：在给定元素上添加只执行一次的事件监听器
 *
 * @param {HTMLElement} el - 元素
 * @param {string} event - 事件名称
 * @param {EventListener} fn - 事件处理函数
 * @returns {void}
 */
export function once(el: HTMLElement, event: string, fn: EventListener): void {
  const listener = function (this: any, ...args: unknown[]) {
    if (fn) {
      fn.apply(this, args);
    }
    off(el, event, listener);
  };
  on(el, event, listener);
}

/**
 * 函数名：useRafThrottle
 * 功能：将给定的函数包装为使用 requestAnimationFrame 实现的节流函数
 *
 * @param {T} fn - 要包装的函数
 * @returns {T} - 包装后的函数
 */
export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
  let locked = false;
  // @ts-ignore
  return function (...args: any[]) {
    if (locked) return;
    locked = true;
    window.requestAnimationFrame(() => {
      // @ts-ignore
      fn.apply(this, args);
      locked = false;
    });
  };
}
